package com.tanger.auth.config;

import com.tanger.auth.mapper.AuthMapper;
import com.tanger.auth.provider.PasswordTokenGranter;
import com.tanger.auth.provider.SmsTokenGranter;
import com.tanger.auth.provider.SocialTokenGranter;
import com.tanger.common.redis.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 资源认证配置
 * @author Hchenbin
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
   private TokenStore tokenStore = new InMemoryTokenStore();//token缓存策略:内存存储
   private static final String REFRESH_TOKEN = "refresh_token";
   private static final String CLIENT_CREDENTIALS = "client_credentials";
   private static final String LOGIN_METHOD = "password";
   private static final String SERVER = "server";
   private static final String SECRET = "123456";
   @Autowired
   @Qualifier("authenticationManagerBean")
   private AuthenticationManager authenticationManager;
   @Autowired
   private AuthMapper authMapper;
   @Autowired
   private RedisUtil redisUtils;
   @Autowired
   private WebResponseExceptionTranslator webResponseExceptionTranslator;
   private TokenGranter tokenGranter;
   @Autowired
   @Qualifier("userDetailsServiceHandle")
   private UserDetailsService userDetailsService;
   private AuthorizationServerTokenServices tokenServices;
   private AccessTokenConverter accessTokenConverter;
   private boolean reuseRefreshToken = true;
   private TokenEnhancer tokenEnhancer;
   private AuthorizationCodeServices authorizationCodeServices;
   private OAuth2RequestFactory requestFactory;
//   @Autowired
//   private TokenStore tokenStore;//token缓存策略:redis存储(配合集群部署)

   @Override
   public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
      clientDetailsServiceConfigurer
              .inMemory()
              .withClient("browser")
                  .authorizedGrantTypes(new String[]{REFRESH_TOKEN, LOGIN_METHOD})
                  .scopes(new String[]{"ui"})
              .and()
              .withClient("wap")
                  .authorizedGrantTypes(new String[]{REFRESH_TOKEN, LOGIN_METHOD})
                  .scopes(new String[]{"app"})
              .and()
              .withClient("brt-base-auth")
                  .secret(SECRET).authorizedGrantTypes(new String[]{CLIENT_CREDENTIALS, REFRESH_TOKEN})
                  .scopes(new String[]{SERVER});
      clientDetailsServiceConfigurer.withClientDetails(this.clientDetailsService());
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception {
      authorizationServerEndpointsConfigurer
              .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
              .pathMapping("/oauth/token","/tanger/oauth/authorize/token")//映射登录地址
              .tokenStore(this.tokenStore)
              .tokenGranter(this.tokenGranter())
              .authenticationManager(this.authenticationManager)
              .userDetailsService(userDetailsService).exceptionTranslator(this.webResponseExceptionTranslator);
   }

   @Override
   public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
      authorizationServerSecurityConfigurer
              .tokenKeyAccess("permitAll()")
              .checkTokenAccess("isAuthenticated()")
              .allowFormAuthenticationForClients()
              .passwordEncoder(NoOpPasswordEncoder.getInstance());
   }


   private TokenGranter tokenGranter() {
      if (this.tokenGranter == null) {
         this.tokenGranter = new TokenGranter() {
            private CompositeTokenGranter delegate;

            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
               if (this.delegate == null) {
                  this.delegate = new CompositeTokenGranter(
                		  OAuth2AuthorizationConfig.
                		  this.getDefaultTokenGranters());
               }

               return this.delegate.grant(grantType, tokenRequest);
            }
         };
      }

      return this.tokenGranter;
   }

   private List<TokenGranter> getDefaultTokenGranters() {
      ClientDetailsService clientDetails = this.clientDetailsService();
      AuthorizationServerTokenServices authorizationServerTokenServices = this.tokenServices();
      AuthorizationCodeServices defaultAuthorizationCodeServices = this.authorizationCodeServices();
      OAuth2RequestFactory oauth2RequestFactory = this.requestFactory();
      List<TokenGranter> tokenGranters = new ArrayList();
      tokenGranters.add(new AuthorizationCodeTokenGranter(authorizationServerTokenServices, defaultAuthorizationCodeServices, clientDetails, oauth2RequestFactory));
      tokenGranters.add(new RefreshTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory));
      ImplicitTokenGranter implicit = new ImplicitTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory);
      tokenGranters.add(implicit);
      tokenGranters.add(new ClientCredentialsTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory));
      if (this.authenticationManager != null) {
         //密码
         tokenGranters.add(new PasswordTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory, this.authMapper, this.redisUtils));
      }

      //短信验证码
      tokenGranters.add(new SmsTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory, this.authMapper, this.redisUtils));
      //第三方
      tokenGranters.add(new SocialTokenGranter(authorizationServerTokenServices, clientDetails, oauth2RequestFactory, this.authMapper, this.redisUtils));
      return tokenGranters;
   }

   private ClientDetailsService clientDetailsService() {
      return new ClientDetailsService() {
         public ClientDetails loadClientByClientId(String clientId) {
            BaseClientDetails details = new BaseClientDetails();
            details.setClientId(clientId);
            details.setAuthorizedGrantTypes(Arrays.asList("refresh_token", "password", "client_credentials", "authorization_code", "implicit", "mobile", "social"));
            if (clientId.equals("wap")) {
               details.setScope(Arrays.asList("app", "server"));
            } else if (clientId.equals("browser")) {
               details.setScope(Arrays.asList("ui", "server"));
            }

            return details;
         }
      };
   }

   private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
      if (userDetailsService != null) {
         PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
         provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(userDetailsService));
         tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
      }

   }

   private AuthorizationServerTokenServices tokenServices() {
      if (this.tokenServices != null) {
         return this.tokenServices;
      } else {
         this.tokenServices = this.createDefaultTokenServices();
         return this.tokenServices;
      }
   }

   private DefaultTokenServices createDefaultTokenServices() {
      DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
      defaultTokenServices.setTokenStore(this.tokenStore());
      defaultTokenServices.setSupportRefreshToken(true);
      defaultTokenServices.setReuseRefreshToken(this.reuseRefreshToken);
      defaultTokenServices.setClientDetailsService(this.clientDetailsService());
      defaultTokenServices.setTokenEnhancer(this.tokenEnhancer());
      this.addUserDetailsService(defaultTokenServices, this.userDetailsService);
      return defaultTokenServices;
   }

   private TokenStore tokenStore() {
      if (this.tokenStore == null) {
         if (this.accessTokenConverter() instanceof JwtAccessTokenConverter) {
            this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter)this.accessTokenConverter());
         } else {
            this.tokenStore = new InMemoryTokenStore();
         }
      }

      return this.tokenStore;
   }

   private AccessTokenConverter accessTokenConverter() {
      if (this.accessTokenConverter == null) {
         this.accessTokenConverter = new DefaultAccessTokenConverter();
      }

      return this.accessTokenConverter;
   }

   private TokenEnhancer tokenEnhancer() {
      if (this.tokenEnhancer == null && this.accessTokenConverter() instanceof JwtAccessTokenConverter) {
         this.tokenEnhancer = (TokenEnhancer)this.accessTokenConverter;
      }

      return this.tokenEnhancer;
   }

   private AuthorizationCodeServices authorizationCodeServices() {
      if (this.authorizationCodeServices == null) {
         this.authorizationCodeServices = new InMemoryAuthorizationCodeServices();
      }

      return this.authorizationCodeServices;
   }

   private OAuth2RequestFactory requestFactory() {
      if (this.requestFactory != null) {
         return this.requestFactory;
      } else {
         this.requestFactory = new DefaultOAuth2RequestFactory(this.clientDetailsService());
         return this.requestFactory;
      }
   }

   @Bean
   public WebResponseExceptionTranslator webResponseExceptionTranslator(){
      return new DefaultWebResponseExceptionTranslator();
   }

}